package org.etom.impl.ui.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PushbackInputStream;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.etom.connector.datatypes.IMoodleModule;
import org.etom.connector.service.ConnectorException;
import org.osgi.framework.BundleContext;

/**
 * The resource manager is responsible for
 * Eclipse resources.
 */
public class ResourceManager {
  
  private static final String RESOURCES_STORAGE = "tmp.resources"; //$NON-NLS-1$
  private static final String ZIP_FILE_EXTENSION = ".zip"; //$NON-NLS-1$
  
  private BundleContext context;
  private File resourcesRootDir;
  
  /**
   * Constructs the new resource manager.
   * 
   * @param context - the bundle context which will be used
   *  to receive the data file.
   *  
   * @throws IOException - if the resource manager can not be initialized
   */
  public ResourceManager(BundleContext context) throws IOException {
    this.context = context;
    initResourcesRoot();
  }
  
  /**
   * The method compresses Eclipse resources to the returned file.
   * 
   * @param resources - Eclipse resources
   * @param fileName - the file name
   * 
   * @return - the compressed file
   * 
   * @throws CoreException - if an error is occurred
   * @throws IOException - if an error is occurred
   */
  public File compressResources(List<?> resources, String fileName) throws CoreException, IOException {
    File tmpFile = getTmpFile(fileName + ZIP_FILE_EXTENSION);
    ZipOutputStream zipOut = new ZipOutputStream(
        new BufferedOutputStream(
        new FileOutputStream(tmpFile)));
    IFile currentFile;
    ZipEntry currentZipEntry;
    BufferedInputStream buffIn = null;
    byte[] readBuff = new byte[4096];
    int readBytes;
    try {
      for (Object resObj : resources) {
        if (!(resObj instanceof IFile)) continue;
        currentFile = ((IFile) resObj);
        currentZipEntry = getZipEntry(currentFile);
        zipOut.putNextEntry(currentZipEntry);
        try {
          buffIn = new BufferedInputStream(currentFile.getContents(false));
          while (-1 != (readBytes = buffIn.read(readBuff))) {
            zipOut.write(readBuff, 0, readBytes);
          }
        } finally {
          if (buffIn != null) buffIn.close();
        }
      }
    } finally {
      if (zipOut != null) zipOut.close();
    }
    return tmpFile;
  }
  
  /**
   * The method copies the source to the given path.
   * 
   * @param source - the source of the copy operation
   * @param dest - the destination of the copy operation
   * 
   * @throws ConnectorException - if an error is occurred
   * @throws IOException - if an error is occurred
   * @throws CoreException - if an error is occurred
   */
  public static void copyInto(IMoodleModule source, IPath dest) throws ConnectorException, IOException, CoreException {
    IFile destFile = ResourcesPlugin.getWorkspace().getRoot().getFile(dest);
    NullProgressMonitor progressMonitor = new NullProgressMonitor();
    if (destFile.exists()) destFile.delete(true, progressMonitor);
    destFile.create(source.getContent(), true, progressMonitor);
  }
  
  /**
   * The method creates new Moodle project from the given resource.
   * 
   * @param project - the project which will be created
   * @param resource - the resource of the content for the project
   * @throws Exception
   */
  public static void createMoodleProject(IProject project, IMoodleModule resource) throws Exception {
    if (!project.isOpen()) project.open(new NullProgressMonitor());
    copyToProject(project, resource);
  }
  
  /**
   * The method creates new Moodle project.
   * 
   * @param projectName - the project name
   * @param location - the project location
   * @param resource - the resource for the project content
   * 
   * @return - the created project
   * 
   * @throws Exception - if the project can not be created
   */
  public static IProject createProject(String projectName, String location, IMoodleModule resource) throws Exception {
    IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
    IProgressMonitor progressMonitor = new NullProgressMonitor();
    if (!project.exists()) {
      if (location != null) {
        IProjectDescription projectDescription = ResourcesPlugin.getWorkspace().newProjectDescription(projectName);
        projectDescription.setLocation(new Path(location));
        project.create(projectDescription, progressMonitor);
      } else {
        project.create(progressMonitor);
      }
    }
    try {
      createMoodleProject(project, resource);
    } catch (Exception e) {
      project.delete(true, true, progressMonitor);
      throw e;
    }
    return project;
  }
  
  /*
   * An utility method which copies the module to the project.
   */
  private static void copyToProject(IProject project, IMoodleModule module)
                        throws IOException, CoreException, ConnectorException {
    PushbackInputStream moduleInStream = new PushbackInputStream(
        new BufferedInputStream(module.getContent()), 2);
    NullProgressMonitor progressMonitor = new NullProgressMonitor();
    if (!ZipUtility.isZipInputStream(moduleInStream)) {
      IFile currentProjectFile = project.getFile(module.getName());
      currentProjectFile.create(moduleInStream, true, progressMonitor);
      moduleInStream.close();
    } else {
      ProjectFileZipInputStream zipInStream = new ProjectFileZipInputStream(moduleInStream);
      ZipEntry nextZipEntry;
      while ((nextZipEntry = zipInStream.getNextEntry()) != null) {
        if (!nextZipEntry.isDirectory()) {
          IFile currentProjectFile = project.getFile(nextZipEntry.getName());
          if (!currentProjectFile.exists()) {
            createParents(currentProjectFile.getParent());
            currentProjectFile.create(zipInStream, true, progressMonitor);
          }
        } else {
          IFolder currentProjectFolder = project.getFolder(nextZipEntry.getName());
          if (!currentProjectFolder.exists()) {
            createParents(currentProjectFolder.getParent());
            currentProjectFolder.create(true, true, progressMonitor);
          }
        }
      }
      zipInStream.closeStream();
    }
  }
  
  /*
   * The method creates an parents of the container.
   */
  private static void createParents(IContainer container) throws CoreException {
    if (!(container instanceof IFolder)) {
      return;
    }
    IFolder folder = (IFolder) container;
    if (!folder.exists()) {
      createParents(folder.getParent());
      folder.create(true, true, new NullProgressMonitor());
    }
  }

  /*
   * The method returns the temporary file.
   */
  private File getTmpFile(String fileName) {
    File tmpFile = context.getDataFile(
        resourcesRootDir.getName() + File.separator + fileName);
    if (tmpFile.exists()) tmpFile.delete();
    return tmpFile;
  }
  
  /*
   * The method creates a new zip entry.
   */
  private ZipEntry getZipEntry(IFile file) {
    ZipEntry zipEntry = new ZipEntry(file.getFullPath().makeRelative().toString());
    //TODO: set CRC and size, see ZipFileExporter
    return zipEntry;
  }
  
  /*
   * The method initializes the resources root.
   */
  private void initResourcesRoot() throws IOException {
    resourcesRootDir = context.getDataFile(RESOURCES_STORAGE);
    if (resourcesRootDir == null) {
      throw new IOException(CommonMessages.Res_Storage_Init_Fail);
    }
    if (resourcesRootDir.exists()) {
      if (!Utility.deleteDir(resourcesRootDir, false))
        throw new IOException(CommonMessages.Res_Storage_Clear_Fail);
    } else {
      if (!resourcesRootDir.mkdir())
        throw new IOException(CommonMessages.Res_Storage_Create_Fail);
    }
  }
  
}
